package org.wartremover

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.plugins.PluginPhase
import dotty.tools.dotc.plugins.StandardPlugin
import dotty.tools.dotc.typer.TyperPhase
import java.io.File
import java.net.URI
import java.net.URLClassLoader
import java.util.concurrent.atomic.AtomicBoolean
import scala.reflect.NameTransformer

object Plugin {
  private[wartremover] def loadWart(
    name: String,
    classLoader: ClassLoader
  ): Either[(String, Throwable), WartTraverser] = {
    try {
      val clazz = classLoader.loadClass(name + NameTransformer.MODULE_SUFFIX_STRING)
      val field = clazz.getField(NameTransformer.MODULE_INSTANCE_NAME)
      val instance = field.get(null)
      Right(instance.asInstanceOf[WartTraverser])
    } catch {
      case e: Throwable => Left((name, e))
    }
  }
}

class Plugin extends StandardPlugin with CompilerPluginCompat {
  override def name = "wartremover"

  override def description = "wartremover"

  private val initialLog = new AtomicBoolean(true)

  protected final def initializeWartremoverPlugin(
    options: List[String],
    context: Option[Context]
  ): List[PluginPhase] = {
    val excluded = options.collect { case s"excluded:${path}" =>
      new File(path).getAbsolutePath
    }
    val classPathEntries = options.collect {
      case s"cp:file:${c}" =>
        val f = new File(c)
        f.getCanonicalFile.toURI.toURL
      case s"cp:${c}" =>
        new URI(c).toURL
    }
    val classLoader = new URLClassLoader(classPathEntries.toArray, getClass.getClassLoader)
    val (errors1, errorWarts) = options.collect { case s"traverser:${name}" =>
      Plugin.loadWart(name, classLoader)
    }.partitionMap(identity)
    val (errors2, warningWarts) = options.collect { case s"only-warn-traverser:${name}" =>
      Plugin.loadWart(name, classLoader)
    }.partitionMap(identity)
    val loglevel = options.collect { case s"loglevel:${level}" =>
      LogLevel.map.get(level)
    }.flatten.headOption.getOrElse(LogLevel.Disable)
    val optionsSet = options.toSet
    val throwIfLoadFail = optionsSet.contains("on-wart-load-error:failure")
    val loadFailureWarts = errors1 ++ errors2
    if (throwIfLoadFail && loadFailureWarts.nonEmpty) {
      println(loadFailureWarts.mkString("load failure warts = ", ", ", ""))
      throw loadFailureWarts.head._2
    }
    val allPhases: List[String] = (errorWarts ++ warningWarts).flatMap(_.runsAfter).distinct.toList.sorted
    allPhases.map { phase =>
      new WartremoverPhase(
        errorWarts = errorWarts.filter(_.runsAfter(phase)),
        warningWarts = warningWarts.filter(_.runsAfter(phase)),
        loadFailureWarts = loadFailureWarts,
        excluded = excluded,
        logLevel = loglevel,
        initialLog = initialLog,
        runsAfter = Set(phase),
        phaseName = phase match {
          case TyperPhase.name =>
            WartremoverPhase.defaultWartremoverPhaseName
          case _ =>
            s"${WartremoverPhase.defaultWartremoverPhaseName}-${phase}"
        }
      )
    }
  }
}
